package crawl

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
)

const HeaderContentType = "Content-Type"

type Crawl struct {
	crawler *Crawler
	cOpts   []ClientOption
	rOpts   []RequestOption
	process Processor
	method  string
	url     string
	query   url.Values
	input   io.Reader
	cType   string
}

func NewCrawl() *Crawl {
	return &Crawl{}
}

func With(crawler *Crawler) *Crawl {
	return NewCrawl().With(crawler)
}

func (c *Crawl) With(crawler *Crawler) *Crawl {
	c.crawler = crawler
	return c
}

// Run 执行请求
func (c *Crawl) Run(ctx context.Context) (err error) {
	//构建请求地址
	var u string
	{
		if len(c.query) == 0 {
			u = c.url
		} else {
			if strings.Contains(c.url, "?") {
				u = c.url + "&" + c.query.Encode()
			} else {
				u = c.url + "&" + c.query.Encode()
			}
		}
	}

	//确定请求方法
	var method string
	{
		if c.method == "" {
			method = http.MethodGet
		} else {
			method = c.method
		}
		if method == http.MethodGet {
			c.input = nil
			c.cType = ""
		}
	}

	//构建请求
	var req *http.Request
	{
		if req, err = http.NewRequestWithContext(ctx, method, u, c.input); err != nil {
			return err
		}

		if c.cType != "" {
			req.Header.Set(HeaderContentType, c.cType)
		}

		if c.crawler != nil {
			for _, requestOpt := range c.crawler.rOpts {
				requestOpt.Apply(req)
			}
		}

		for _, requestOpt := range c.rOpts {
			requestOpt.Apply(req)
		}
	}

	//构建客户端
	client := &http.Client{}
	{
		if c.crawler != nil {
			client.Jar = c.crawler.jar
			for _, clientOpt := range c.crawler.cOpts {
				clientOpt.Apply(client)
			}
		}

		for _, clientOpt := range c.cOpts {
			clientOpt.Apply(client)
		}
	}

	//处理响应
	var resp *http.Response
	{
		if resp, err = client.Do(req); err != nil {
			return
		}
		defer closes(resp.Body)
		if c.process != nil {
			func() {
				defer recovers(&err)
				err = c.process(resp)
			}()
		}
	}

	return
}

// Enqueue 将请求添加到队列
func (c *Crawl) Enqueue() *Crawler {
	if c.crawler != nil {
		return c.crawler.Enqueue(c)
	}
	return c.crawler
}

// EnqueueTo 将请求添加到队列
func (c *Crawl) EnqueueTo(crawler *Crawler) *Crawler {
	return c.With(crawler).Enqueue()
}

// ClientOpt 客户端配置
func (c *Crawl) ClientOpt(options ...ClientOption) *Crawl {
	c.cOpts = append(c.cOpts, options...)
	return c
}

// RequestOpt 请求配置
func (c *Crawl) RequestOpt(options ...RequestOption) *Crawl {
	c.rOpts = append(c.rOpts, options...)
	return c
}

// Method 请求方法
func (c *Crawl) Method(method string) *Crawl {
	c.method = method
	return c
}

// Url 请求地址
func (c *Crawl) Url(url string) *Crawl {
	c.url = url
	return c
}

// Query 设置Query参数
func (c *Crawl) Query(query url.Values) *Crawl {
	c.query = query
	return c
}

//QueryAdd 添加Query参数
func (c *Crawl) QueryAdd(key, value string) *Crawl {
	if c.query == nil {
		c.query = url.Values{}
	}
	c.query.Add(key, value)
	return c
}

//QuerySet 设置
func (c *Crawl) QuerySet(key, value string) *Crawl {
	if c.query == nil {
		c.query = url.Values{}
	}
	c.query.Set(key, value)
	return c
}

// SetBody 设置请求数据
func (c *Crawl) SetBody(data io.Reader, contentType string) *Crawl {
	c.input = data
	c.cType = contentType
	return c
}

//Process 设置处理方法
func (c *Crawl) Process(processor Processor) *Crawl {
	c.process = processor
	return c
}

//Get 请求
func (c *Crawl) Get(url string) *Crawl {
	c.Method(http.MethodGet).Url(url).SetBody(nil, "")
	return c
}

//GetBytes 请求并将结果读取为字节组
func (c *Crawl) GetBytes(url string, data *[]byte) *Crawl {
	return c.Get(url).Process(ProcessBytes(func(v []byte) error {
		*data = v
		return nil
	}))
}

//GetJSON 请求并将结果以JSON方式解析到对象
func (c *Crawl) GetJSON(url string, out interface{}) *Crawl {
	return c.Get(url).Process(ProcessJSON(out))
}

//PostForm 表单提交请求
func (c *Crawl) PostForm(url string, data url.Values) *Crawl {
	return c.Method(http.MethodPost).Url(url).SetBody(FormInput(data))
}

//PostJSON JSON提交请求
func (c *Crawl) PostJSON(url string, data interface{}) *Crawl {
	return c.Method(http.MethodPost).Url(url).SetBody(JSONInput(data))
}

//PostXML XML提交请求
func (c *Crawl) PostXML(url string, data interface{}) *Crawl {
	return c.Method(http.MethodPost).Url(url).SetBody(XMLInput(data))
}

func closes(closer io.Closer) {
	if closer != nil {
		if err := closer.Close(); err != nil {
			log.Printf("%v", err)
		}
	}
}

func recovers(err *error) {
	if re := recover(); re != nil {
		if ex, ok := re.(error); ok {
			*err = ex
		} else {
			*err = fmt.Errorf("%v", re)
		}
	}
}
